package br.mikhas.util;

import static br.mikhas.util.Assert.notEmpty;
import static br.mikhas.util.Assert.notNull;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.Flushable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * Utility methods to help working with IO operations
 * 
 * @author Mikhail Domanoski
 * 
 */
public class IOUtils {
	/**
	 * Default byte buffer size
	 */
	private static final int BUFFER_SIZE = 2048;

	private IOUtils() {
	}

	/**
	 * Copies the stream from one {@link Reader} to a {@link Writer}
	 * 
	 * @param in
	 *            The input reader
	 * @param out
	 *            The output writer
	 */
	public static void copy(Reader in, Writer out) {
		notNull(in);
		notNull(out);

		char[] buffer = new char[BUFFER_SIZE];
		int count = 0;

		try {
			while ((count = in.read(buffer, 0, BUFFER_SIZE)) != -1) {
				out.write(buffer, 0, count);
			}

			close(in);
			close(out);

		} catch (IOException ex) {
			throw new RuntimeException(ex);
		}
	}

	/**
	 * Copies teh content of one file to another
	 * 
	 * @param in
	 *            The file to be copied
	 * @param out
	 *            The destination of the file
	 * @throws IOException
	 */
	public static void copy(File in, File out) {
		notNull(in);
		notNull(out);

		try {
			FileOutputStream fos = new FileOutputStream(out);
			FileInputStream fis = new FileInputStream(in);

			internalCopy(fis, fos, true);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Copies data from an {@link InputStream} to an {@link OutputStream}
	 * 
	 * @param in
	 *            The source of the data
	 * @param out
	 *            The destiny of the data
	 */
	public static void copy(InputStream in, OutputStream out) {
		internalCopy(in, out, true);
	}

	/**
	 * Copies data from an {@link InputStream} to an {@link OutputStream}
	 * 
	 * @param in
	 *            The source of the data
	 * @param out
	 *            The destiny of the data
	 * @param close
	 *            Flags if the streams shoud be closed after copy
	 */
	private static void internalCopy(InputStream in, OutputStream out,
			boolean close) {
		notNull(in);
		notNull(out);

		byte[] buffer = new byte[BUFFER_SIZE];
		int count;

		try {

			while ((count = in.read(buffer, 0, BUFFER_SIZE)) != -1) {
				out.write(buffer, 0, count);
			}

		} catch (IOException e) {
			throw new RuntimeException(e);
		} finally {
			if (close) {
				close(in);
				close(out);
			}
		}
	}

	/**
	 * Saves an input stream data in a zip file.
	 * <p>
	 * The name of the entry will be the same as the zipfile name.
	 * 
	 * @param in
	 *            The the file to be compressed
	 * @param zipfile
	 *            The output zip file
	 */
	public static void zip(File in, File zipfile) {
		notNull(in);
		if (in.exists() && in.isFile())
			try {
				zip(new FileInputStream(in), in.getName(), zipfile);
			} catch (FileNotFoundException e) {
				throw new RuntimeException(e);
			}
	}

	/**
	 * Saves an input stream data in a zip file
	 * 
	 * @param in
	 *            The input stream which contains the data
	 * @param entryName
	 *            The name of the entry to be zipped
	 * @param zipfile
	 *            The output zip file
	 */
	public static void zip(InputStream in, String entryName, File zipfile) {
		notNull(in);
		notNull(zipfile);
		notEmpty(entryName);

		try {
			ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(
					zipfile));

			zip.putNextEntry(new ZipEntry(entryName));

			internalCopy(in, zip, true);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Unzips a zip file and transfer to an {@link OutputStream}
	 * 
	 * @param zipfile
	 *            The zip file to extracts
	 * @param out
	 *            The output stream to transfer the data
	 */
	public static void unzip(File zipfile, OutputStream out) {
		unzip(zipfile, zipfile.getName(), out);
	}

	/**
	 * Unzips a zip file and transfer to an {@link OutputStream}
	 * 
	 * @param zipfile
	 *            The zip file to extracts
	 * @param entryName
	 *            The name of the zip entry
	 * @param out
	 *            The output stream to transfer the data
	 */
	public static void unzip(File zipfile, String entryName, OutputStream out) {
		notEmpty(entryName);
		notNull(out);
		notNull(zipfile);
		if (zipfile.exists() && zipfile.isFile())
			try {
				ZipInputStream zip = new ZipInputStream(new FileInputStream(
						zipfile));

				ZipEntry entry;

				while ((entry = zip.getNextEntry()) != null) {
					if (entryName.equals(entry.getName())) {
						internalCopy(zip, out, true);
						return;
					}
				}

			} catch (IOException e) {
				throw new RuntimeException(e);
			}
	}

	/**
	 * Null-safe resource flushing and closing.
	 * 
	 * @param closeable
	 *            resource
	 */
	public static void close(Closeable closeable) {
		if (closeable != null)
			try {
				if (closeable instanceof Flushable) {
					try {
						((Flushable) closeable).flush();
					} catch (Throwable tt) {
					}
				}
				closeable.close();
			} catch (Throwable t) {

			}
	}
}
